const classic_rules = {
        name : "classic 9x9x9",
        candidIDs : ['1','2','3','4','5','6','7','8','9'],
        cellIDs : [
                'c00','c01','c02','c03','c04','c05','c06','c07','c08',
                'c09','c10','c11','c12','c13','c14','c15','c16','c17',
                'c18','c19','c20','c21','c22','c23','c24','c25','c26',
                'c27','c28','c29','c30','c31','c32','c33','c34','c35',
                'c36','c37','c38','c39','c40','c41','c42','c43','c44',
                'c45','c46','c47','c48','c49','c50','c51','c52','c53',
                'c54','c55','c56','c57','c58','c59','c60','c61','c62',
                'c63','c64','c65','c66','c67','c68','c69','c70','c71',
                'c72','c73','c74','c75','c76','c77','c78','c79','c80'],
        houseIDs : [
                'hr0','hr1','hr2','hr3','hr4','hr5','hr6','hr7','hr8',
                'hc0','hc1','hc2','hc3','hc4','hc5','hc6','hc7','hc8',
                'hb0','hb1','hb2','hb3','hb4','hb5','hb6','hb7','hb8'],
        cellsToHouses: {
                "c00":["hr0","hc0","hb0"],"c01":["hr0","hc1","hb0"],"c02":["hr0","hc2","hb0"],
                "c03":["hr0","hc3","hb1"],"c04":["hr0","hc4","hb1"],"c05":["hr0","hc5","hb1"],
                "c06":["hr0","hc6","hb2"],"c07":["hr0","hc7","hb2"],"c08":["hr0","hc8","hb2"],
                "c09":["hr1","hc0","hb0"],"c10":["hr1","hc1","hb0"],"c11":["hr1","hc2","hb0"],
                "c12":["hr1","hc3","hb1"],"c13":["hr1","hc4","hb1"],"c14":["hr1","hc5","hb1"],
                "c15":["hr1","hc6","hb2"],"c16":["hr1","hc7","hb2"],"c17":["hr1","hc8","hb2"],
                "c18":["hr2","hc0","hb0"],"c19":["hr2","hc1","hb0"],"c20":["hr2","hc2","hb0"],
                "c21":["hr2","hc3","hb1"],"c22":["hr2","hc4","hb1"],"c23":["hr2","hc5","hb1"],
                "c24":["hr2","hc6","hb2"],"c25":["hr2","hc7","hb2"],"c26":["hr2","hc8","hb2"],
                "c27":["hr3","hc0","hb3"],"c28":["hr3","hc1","hb3"],"c29":["hr3","hc2","hb3"],
                "c30":["hr3","hc3","hb4"],"c31":["hr3","hc4","hb4"],"c32":["hr3","hc5","hb4"],
                "c33":["hr3","hc6","hb5"],"c34":["hr3","hc7","hb5"],"c35":["hr3","hc8","hb5"],
                "c36":["hr4","hc0","hb3"],"c37":["hr4","hc1","hb3"],"c38":["hr4","hc2","hb3"],
                "c39":["hr4","hc3","hb4"],"c40":["hr4","hc4","hb4"],"c41":["hr4","hc5","hb4"],
                "c42":["hr4","hc6","hb5"],"c43":["hr4","hc7","hb5"],"c44":["hr4","hc8","hb5"],
                "c45":["hr5","hc0","hb3"],"c46":["hr5","hc1","hb3"],"c47":["hr5","hc2","hb3"],
                "c48":["hr5","hc3","hb4"],"c49":["hr5","hc4","hb4"],"c50":["hr5","hc5","hb4"],
                "c51":["hr5","hc6","hb5"],"c52":["hr5","hc7","hb5"],"c53":["hr5","hc8","hb5"],
                "c54":["hr6","hc0","hb6"],"c55":["hr6","hc1","hb6"],"c56":["hr6","hc2","hb6"],
                "c57":["hr6","hc3","hb7"],"c58":["hr6","hc4","hb7"],"c59":["hr6","hc5","hb7"],
                "c60":["hr6","hc6","hb8"],"c61":["hr6","hc7","hb8"],"c62":["hr6","hc8","hb8"],
                "c63":["hr7","hc0","hb6"],"c64":["hr7","hc1","hb6"],"c65":["hr7","hc2","hb6"],
                "c66":["hr7","hc3","hb7"],"c67":["hr7","hc4","hb7"],"c68":["hr7","hc5","hb7"],
                "c69":["hr7","hc6","hb8"],"c70":["hr7","hc7","hb8"],"c71":["hr7","hc8","hb8"],
                "c72":["hr8","hc0","hb6"],"c73":["hr8","hc1","hb6"],"c74":["hr8","hc2","hb6"],
                "c75":["hr8","hc3","hb7"],"c76":["hr8","hc4","hb7"],"c77":["hr8","hc5","hb7"],
                "c78":["hr8","hc6","hb8"],"c79":["hr8","hc7","hb8"],"c80":["hr8","hc8","hb8"]},
        houses: {
                'hr0': ['c00','c01','c02','c03','c04','c05','c06','c07','c08'],
                'hr1': ['c09','c10','c11','c12','c13','c14','c15','c16','c17'],
                'hr2': ['c18','c19','c20','c21','c22','c23','c24','c25','c26'],
                'hr3': ['c27','c28','c29','c30','c31','c32','c33','c34','c35'],
                'hr4': ['c36','c37','c38','c39','c40','c41','c42','c43','c44'],
                'hr5': ['c45','c46','c47','c48','c49','c50','c51','c52','c53'],
                'hr6': ['c54','c55','c56','c57','c58','c59','c60','c61','c62'],
                'hr7': ['c63','c64','c65','c66','c67','c68','c69','c70','c71'],
                'hr8': ['c72','c73','c74','c75','c76','c77','c78','c79','c80'],
                'hc0': ['c00','c09','c18','c27','c36','c45','c54','c63','c72'],
                'hc1': ['c01','c10','c19','c28','c37','c46','c55','c64','c73'],
                'hc2': ['c02','c11','c20','c29','c38','c47','c56','c65','c74'],
                'hc3': ['c03','c12','c21','c30','c39','c48','c57','c66','c75'],
                'hc4': ['c04','c13','c22','c31','c40','c49','c58','c67','c76'],
                'hc5': ['c05','c14','c23','c32','c41','c50','c59','c68','c77'],
                'hc6': ['c06','c15','c24','c33','c42','c51','c60','c69','c78'],
                'hc7': ['c07','c16','c25','c34','c43','c52','c61','c70','c79'],
                'hc8': ['c08','c17','c26','c35','c44','c53','c62','c71','c80'],
                'hb0': ['c00','c01','c02','c09','c10','c11','c18','c20','c19'],
                'hb1': ['c03','c04','c05','c12','c13','c14','c21','c22','c23'],
                'hb2': ['c06','c07','c08','c15','c16','c17','c24','c25','c26'],
                'hb3': ['c27','c28','c29','c36','c37','c38','c45','c46','c47'],
                'hb4': ['c30','c31','c32','c39','c40','c41','c48','c49','c50'],
                'hb5': ['c33','c34','c35','c42','c43','c44','c51','c52','c53'],
                'hb6': ['c54','c55','c56','c63','c64','c65','c72','c73','c74'],
                'hb7': ['c57','c58','c59','c66','c67','c68','c75','c76','c77'],
                'hb8': ['c60','c61','c62','c69','c70','c71','c78','c79','c80']
        },
        cellOffsets :{
                'c00':{y:0,x:0,},'c01':{y:0,x:11.11,},'c02':{y:0,x:22.22,},
                'c03':{y:0,x:33.33,},'c04':{y:0,x:44.44,},'c05':{y:0,x:55.55,},
                'c06':{y:0,x:66.66,},'c07':{y:0,x:77.77,},'c08':{y:0,x:88.88,},
                'c09':{y:11.11,x:0,},'c10':{y:11.11,x:11.11,},'c11':{y:11.11,x:22.22,},
                'c12':{y:11.11,x:33.33,},'c13':{y:11.11,x:44.44,},'c14':{y:11.11,x:55.55,},
                'c15':{y:11.11,x:66.66,},'c16':{y:11.11,x:77.77,},'c17':{y:11.11,x:88.88,},
                'c18':{y:22.22,x:0,},'c19':{y:22.22,x:11.11,},'c20':{y:22.22,x:22.22,},
                'c21':{y:22.22,x:33.33,},'c22':{y:22.22,x:44.44,},'c23':{y:22.22,x:55.55,},
                'c24':{y:22.22,x:66.66,},'c25':{y:22.22,x:77.77,},'c26':{y:22.22,x:88.88,},
                'c27':{y:33.33,x:0,},'c28':{y:33.33,x:11.11,},'c29':{y:33.33,x:22.22,},
                'c30':{y:33.33,x:33.33,},'c31':{y:33.33,x:44.44,},'c32':{y:33.33,x:55.55,},
                'c33':{y:33.33,x:66.66,},'c34':{y:33.33,x:77.77,},'c35':{y:33.33,x:88.88,},
                'c36':{y:44.44,x:0,},'c37':{y:44.44,x:11.11,},'c38':{y:44.44,x:22.22,},
                'c39':{y:44.44,x:33.33,},'c40':{y:44.44,x:44.44,},'c41':{y:44.44,x:55.55,},
                'c42':{y:44.44,x:66.66,},'c43':{y:44.44,x:77.77,},'c44':{y:44.44,x:88.88,},
                'c45':{y:55.55,x:0,},'c46':{y:55.55,x:11.11,},'c47':{y:55.55,x:22.22,},
                'c48':{y:55.55,x:33.33,},'c49':{y:55.55,x:44.44,},'c50':{y:55.55,x:55.55,},
                'c51':{y:55.55,x:66.66,},'c52':{y:55.55,x:77.77,},'c53':{y:55.55,x:88.88,},
                'c54':{y:66.66,x:0,},'c55':{y:66.66,x:11.11,},'c56':{y:66.66,x:22.22,},
                'c57':{y:66.66,x:33.33,},'c58':{y:66.66,x:44.44,},'c59':{y:66.66,x:55.55,},
                'c60':{y:66.66,x:66.66,},'c61':{y:66.66,x:77.77,},'c62':{y:66.66,x:88.88,},
                'c63':{y:77.77,x:0,},'c64':{y:77.77,x:11.11,},'c65':{y:77.77,x:22.22,},
                'c66':{y:77.77,x:33.33,},'c67':{y:77.77,x:44.44,},'c68':{y:77.77,x:55.55,},
                'c69':{y:77.77,x:66.66,},'c70':{y:77.77,x:77.77,},'c71':{y:77.77,x:88.88,},
                'c72':{y:88.88,x:0,},'c73':{y:88.88,x:11.11,},'c74':{y:88.88,x:22.22,},
                'c75':{y:88.88,x:33.33,},'c76':{y:88.88,x:44.44,},'c77':{y:88.88,x:55.55,},
                'c78':{y:88.88,x:66.66,},'c79':{y:88.88,x:77.77,},'c80':{y:88.88,x:88.88,}},
        candidOffsets:{
                1:{x:12,y:6},
                2:{x:42,y:6},
                3:{x:72,y:6},
                4:{x:12,y:36},
                5:{x:42,y:36},
                6:{x:72,y:36},
                7:{x:12,y:66},
                8:{x:42,y:66},
                9:{x:72,y:66},
        },
}

function parse( sz){
        var field = new Array(),
                id,
                candid,
                houses=new Array();
        for( var i=0; i<sz.length; i++) {
                houses = new Array()
                id= classic_rules.cellIDs[i];
                candid = sz[i]=="0"? classic_rules.candidIDs :sz[i];
                for( var [hid,cids] in Iterator( classic_rules.houses))
                        if( !!~cids.indexOf( id))
                                houses.push(hid);
                field.push( new sdkCell(id, candid, houses));
        }
        return field;
}


function sdkCell(){
        this.id = null;
        this.candids = null;
        this.houses = null;
        switch( arguments.length){
                case 1:
                        if( typeof arguments[0] == "string" || arguments[0] instanceof String)
                                this.id = arguments[0];
                        else {
                                this.id = arguments[0].id || null;
                                this.candids = arguments[0].candids || null;
                                this.houses = arguments[0].houses || null;
                        }
                        break;
                case 2:
                        this.id = arguments[0];
                        if( typeof arguments[1] == "array" || arguments[1] instanceof Array)
                                this.candids = arguments[1];
                        else
                                this.candids = new Array( arguments[1]);
                        break;
                case 3:
                        this.id = arguments[0];
                        if( typeof arguments[1] == "array" || arguments[1] instanceof Array)
                                this.candids = arguments[1];
                        else
                                this.candids = new Array( arguments[1]);
                        this.houses = arguments[2];
                        break;
        }
       
        this.contains = function ( candid) {
                return !!~this.candids.indexOf(candid);
        };
       
        this.isSolved = function (){
                return this.candids.length == 1;
        };
       
        this.enable = function( candid){
                if( !this.contains( candid))
                        this.candids.push( candid);
        };
       
        this.disable = function( candid){
                if( candid instanceof Array)
                        candid.forEach(function(CandidId){
                                this.disable(CandidId);
                        },this);
                else
                        this.candids = this.candids.filter(function(Candid){
                                return Candid != candid;
                        });
        };
       
        this.only = function( candid){
                if( this.candids.length == 1){
                        this.candids=classic_rules.candidIDs;
                        return this;
                }
                this.candids = new Array(1);
                this.candids[0] = candid;
               
                return this;
        };
       
        this.swap = function ( candid){
                if( this.contains( candid))
                        this.disable( candid);
                else
                        this.enable( candid);
                return this;
        };
       
        this.isInHouse = function( houseId){
                return !!~this.houses.indexOf( houseId );
        };
       
        this.applyHouses = function( new_houses){
                this.houses = new Array();
                for( var i =0; i< new_houses.length; i++){
                        if( new_houses[ i].indexOf( this.id) >= 0)
                                this.houses.push(i);
                }
        };
}


function sdkChange( cmd, cellIDs, candidIDs){
        return {
                cmd: cmd,
                cellIDs: cellIDs,
                candidIDs: candidIDs,
                apply: function( slice){
                        var a = sdkSlice( slice.filterBy(function(cell){
                                return !!~this.cellIDs.indexOf( cell.id);
                        },this));
                       
                        var b = slice.filterBy(function(cell){
                                return !~this.cellIDs.indexOf( cell.id);
                        },this);
                       
                        if( cmd === "swap")
                                a=a.swap( this.candidIDs[0]);
                               
                        if( cmd === "only")
                                a=a.only( this.candidIDs[0]);
                        b=b.concat(a);
                        return sdkSlice(b);
                }
        };
}

function sdkSlice( data){
        sdkHelper.makeSlice__.call( data);
        return data;
}

function sliceBase(){
               
        this.isSlice = function(){
                return true;
        };
       
        /*this.calcHouses = function(){
                var h2 = new Array();
                this.forEach(function(cell){
                        h2=h2.concat( cell.houses);
                },this);
               
                this.houses = sdkHelper.makeUnique( h2 );
        };*/
       
        this.houseIterator = function(){
                return sdkHelper.makeUnique( this.houses);
        };
       
        this.isSolved = function(){
                return this.every(function( cell){
                        cell.isSolved();
                });
        };
       
        this.disable = function( candid){
                return this.forEach( function(cell){
                        cell.disable(candid);
                });
        };
       
        this.swap = function( candidID){
                return sdkSlice(this.map( function(cell){
                        return cell.swap( candidID);
                }));
        };
       
        this.only = function( candidID){
                return sdkSlice(this.map( function(cell){
                        return cell.only( candidID);
                }));
        };
       
        // Filter
        this.filterBy = function( callback, context){
                var temp = [];
                temp = this.filter( callback, context);
                return sdkSlice( temp);
        };
       
        this.singleCell = function( arg){
                return this.filterBy( function( cell){
                                return cell.id == arg;
                        });
        };
       
        this.secludeCell = function( arg){
                return this.filterBy(function(cell){
                                return cell.id != arg;
                        });
        };
       
        this.filterByHouse = function( arg){
                return this.filterBy(function (cell) {
                                return cell.isInHouse( arg);
                        },arg);
        };
       
        this.filterByHouses = function( arg){
                var ret = new Array();          
                var i=0;
                arg.forEach(function( houseId){
                        ret = ret.concat( this.filterByHouse( houseId));
                },this);
                ret = sdkHelper.makeUniqueCells( ret);
                sdkHelper.makeSlice( ret );
                return ret;
        };
       
        this.filterByLength = function( arg){
                return this.filterBy( function (cell) {
                                return cell.candids.length == arg;
                        },arg);
        };
       
        this.filterByCandid = function( arg){
                return this.filterBy( function (cell) {
                                return cell.contains( arg);
                        },arg);
        };
       
        this.filterByCandids = function( arg){
                var ret = this;
                //ret = ret.concat( this);

                arg.forEach(function(candidId){
                        ret = ret.filterByCandid(candidId);
                },this);

                return ret;
        };
       
        // Negations
        this.filterNotHouse = function( arg){
                return this.filterBy(function (cell) {
                                return !cell.isInHouse( arg);
                        },arg);
        };
       
        this.filterNotHouses = function( arg){
                var ret = copy(this);
                arg.forEach(function( houseId){
                        ret.concat( ret.filterNotHouse( houseId));
                });
                return ret;
        };
       
        this.filterNotLength = function( arg){
                return this.filterBy( function (cell) {
                                return cell.candids.length != arg;
                        },arg);
        };
       
        this.filterNotCandid = function( arg){
                return this.filterBy( function (cell) {
                                return !cell.contains( arg);
                        },arg);
        };
       
        this.filterNotCandids = function( arg){
                var ret = this;
                arg.forEach(function(candidId){
                        ret.concat( ret.filterNotCandid(candidId));
                });
                return ret;
        };
       
        //this.calcHouses();
}

var sdkHelper = {
        makeSlice : function ( who) {
                this.makeSlice__.call( who);
                return who;
        },
        makeSlice__ : sliceBase,
       
        makeUnique : function( arr){
                arr = arr.sort();
                var arr2 = new Array();
                for( i =0; i< arr.length; i++){
                        arr2.push( arr[i]);
                        i = arr.lastIndexOf(arr[i]);
                }
                return arr2;
        },
       
        makeUniqueCells : function( arr){
                arr = arr.sort();
               
                var arr2 = new Array();
                for( i =0; i< arr.length; i++){
               
                        arr2.push( arr[i]);

                        var j=0;
                        for( j= i+1; j< arr.length ; j++){
                               
                                if( arr[ j].id != arr[ i].id){
                                        break;
                                }
                        }


                        i=j-1;

                       
                }
                sdkHelper.makeSlice( arr2)
                return arr2;
        },
       
        setUnion : function(a1,a2){
                return sdkHelper.makeUnique(Array.concat(a1,a2));
        },
       
        setIntersection : function( a1, a2){
                var ret = new Array();
                a1.forEach(function(ele){
                        if( !!~a2.indexOf(ele))
                                ret.push(ele);
                });
                return sdkHelper.makeUnique(ret);
        },
       
        setSymDiff : function( a1, a2){
                var ret = new Array();
                a1.forEach(function(ele){
                        if( !~a2.indexOf(ele))
                                ret.push(ele);
                });
                a2.forEach(function(ele){
                        if( !~a1.indexOf(ele))
                                ret.push(ele);
                });
                return sdkHelper.makeUnique(ret);
        },
       
        sharedHouses : function( c1, c2 ){
                var ret = new Array();
                ret = sdkHelper.setIntersection( c1.houses, c2.houses);
                return ret;
        },
       
        sharedCandids : function( c1, c2){
                var ret = new Array();
                for( candidId in c1.candids)
                        if( c2.contains( c2))
                                ret.push( candidId);
                return ret;
        },
};

function subsetCounting( data, set){
        var count = 0;

}

var algors = {
        "Naked Single":function ( data){
                        var ret = 0;
                        data.filterByLength(1).forEach( function(solvedCell){
                                solvedCell.houses.forEach( function(sCHouseId){
                                        data.filterByHouse( sCHouseId).filterNotLength(1).disable( solvedCell.candids[0]);
                                },this);
                        },this);
                       
                        return data;
                },
        "Hidden Single" : function( data){
                        var ret=0;
                        var grml = data;
                        sdkHelper.makeSlice(grml);
                        for each( var house in grml.houseIterator()){
                                for each( candidId in [1,2,3,4,5,6,7,8,9]){
                                        var blub = grml.filterBy(function(cell){
                                                return cell.isInHouse( house) && cell.contains(candidId);
                                        });
                                        //var blub = grml.filterByHouse( house).filterByCandid( candidId);
                                        if( blub.length == 1)
                                                if( blub[0].candids.length != 1){
                                                        blub[0].only( candidId);
                                                        ret++;
                                                }
                                }
                        }
                       
                        return grml;
                }
        };





var field = [];
var history = [];

var handler = [];

function regHandler( cmd, callback, context){
        handler.push( {cmd:cmd,callback:callback,context:context});
}



var intercom = {
        handler: [],
        register: function(cmd, callback, context){
                this.handler.push( {"cmd":cmd,"callback":callback,"context":context});
        },
        catcher : function( e){
                var ret = {},
                        fn = this.handler.filter( function( handle){
                                return handle.cmd === e.data.cmd;
                        },this);
               
                if( fn.length ==0)
                        return "fail.";
                if( fn.length >1)
                        return "too many handlers";
                fn=fn[0];
                try{
                        ret = fn.callback.call(fn.context,e.data);
                }catch(error){
                        note(e.data.cmd);
                       
                        throw(error);
                }
                if(!ret.refid)
                        ret.refid = e.data.refid;
                postMessage( ret);
        }
};

function cl( a){
        postMessage({cmd:"note",args:{value:a}});
}

function note( a){
        postMessage({cmd:"note",args:{value:a}});
}

function ns( a){
        postMessage({cmd:"log",args:{value:a.toSource()}});
}

function ne( a){
        postMessage({cmd:"error",args:{value:a}});
}

function es( a){
        postMessage({cmd:"logerror",args:{value:a}});
}

function tempCatcher(e){
        intercom.catcher(e);
}

onmessage = tempCatcher;

intercom.register("init", function(e){
        field = sdkSlice( parse( e.args.field));
        return getHandler({args:{value:"field"}});
});

function getHandler(e){
        switch( e.args.value){
                case "field":
                        return {
                                cmd: "post_field",
                                args: {
                                        value: field
                                }
                        };
                case "cell":
                        return {
                                cmd: "post_cell",
                                args: {
                                        value: field.singleCell(e.args.cellID)
                                }
                        };
                case "cellIDs":
                        return {
                                cmd: "post_cellIDs",
                                args:{
                                        value: classic_rules.cellIDs
                                }
                        };
                case "candidIDs":
                        return {
                                cmd: "post_candidIDs",
                                args:{
                                        value: classic_rules.candidIDs
                                }
                        };
                case "cellsToHouses":
                        return {
                                cmd: "post_cellToHouses",
                                args:{
                                        value: classic_rules.cellsToHouses
                                }
                        };
                case "all_candids":
                        return {
                                cmd: "post_all_candids",
                                args:{
                                        value: classic_rules.candidIDs
                                }
                        };
                        break;
                default:
                        if(classic_rules[e.args.value]){
                                return {
                                        cmd: "post_"+e.args.value,
                                        args:{
                                                value: classic_rules[e.args.value]
                                        }
                                };
                        }
                        return undefined;
        }
}
intercom.register("get", getHandler);


intercom.register("get_multiple",function(e){
        var ret = {};
        for( var i=0; i<e.args.values.length;i++){
                var req= e.args.values[i];
                ret[req]=getHandler({args:{value:req}}).args.value;
        }
        return {cmd:"post_multiple",args:ret};
});
intercom.register("status",function(e){
        return {
                cmd:"log",
                args:{
                        value:"all clear! :D"
                }
        };
});
intercom.register("change",function(e){
switch( e.args.value){
        case "swap":
                history.unshift(new sdkChange("swap",[e.args.cellID],[e.args.candidID]));
                field = history[0].apply( field);
                return getHandler({args:{value:"cell",cellID:e.args.cellID}});
                break;
        case "only":
                history.unshift(new sdkChange("only",[e.args.cellID],[e.args.candidID]));
                field = history[0].apply( field);
                return getHandler({args:{value:"cell",cellID:e.args.cellID}});
}});
intercom.register("calc",function(e){
        if( algors[e.args.value]){
                field = algors[e.args.value](field);
                return getHandler({args:{value:"field"}});
        }
});
